<template>
  <div class="container">
    <div class="app-container">
      <!-- 角色管理内容 -->
      <div class="role-operate" v-loading="loading">
        <el-button @click="addRole" type="primary">添加角色</el-button>
        <!-- 放置表格组件 -->
        <el-table v-if="roleList.length>0" stripe :data="roleList"  :row-key="roleList.id">
          <el-table-column label="角色" align="center" prop="name">
            <!-- 通过插槽实现不同的内容展现 -->
            <template #default="{row}">
              <!-- 条件判断 -->
              <el-input 
              v-if="row.isEdit" size="mini"
              v-model="row.editRow.name"
              ></el-input>
              <span v-else>{{row.name}}</span>
            </template>
          </el-table-column>
          <el-table-column label="启用" align="center" prop="state">
            <template #default="{row}">
              <el-switch 
              v-if="row.isEdit"
              v-model="row.editRow.state"
              :active-value="1"
              :inactive-value="0"
              ></el-switch>
              <span v-else>{{row.state===1?'已启用':row.state===0?'未启用':'无'}}</span>
            </template>
          </el-table-column>
          <el-table-column label="描述" align="center" prop="description">
            <template #default="{row}">
              <el-input 
              v-if="row.isEdit"
              type="textarea"
              size="mini"
              v-model="row.editRow.description"
              >
              </el-input>
              <span v-else>{{row.description}}</span>
            </template>
            
          </el-table-column>
          <el-table-column label="操作" align="center">
            <!-- 放置操作按钮 -->
            <!-- 给按钮加上type="text"会自动变成链接形式 -->
            <template #default="{row}">
              <!-- 编辑状态 -->
              <template v-if="row.isEdit">
                <el-button
                type="primary"
                size="mini"
                @click="btnEditOk(row)"
                >确定</el-button>
                <el-button
                size="mini"
                @click="row.isEdit=false"

                >取消</el-button>
              </template>
              <!-- 非编辑状态 -->
              <template v-else>
                <el-button
                type="text"
                @click="btnPermission(row.id)"
                >分配权限</el-button>
              <el-button @click="btnEditRow(row)" type="text">编辑</el-button>
              <!-- 气泡确认框 -->
              <el-popconfirm
              confirm-button-text='确定'
              cancel-button-text='取消'
              icon="el-icon-info"
              icon-color="red"
              title="确定删除吗？"
              @onConfirm="confirmDel(row.id)"
              >
              <el-button type="text" style="margin-left:10px;" slot="reference">
                删除</el-button>
              </el-popconfirm>
              </template>
            </template>
          </el-table-column>
        </el-table>
        <!-- 无数据显示空状态(vue2中没有空状态组件) -->
        <div v-else class="empty-state">
        <i class="el-icon-document"></i>
        <p>暂无数据</p>
        </div>
        <!-- 放置分页组件 -->
        <el-row v-if="roleList.length>0" type="flex" justify="center" style="height:60px;" align="middle" >
          <!-- 引入分页组件 -->
          <el-pagination
            background
            layout="prev, pager, next"
            :total="pageParams.total"
            :page-size="pageParams.pagesize"
            :current-page="pageParams.page"
            @current-change="changePage"
            >
          </el-pagination>
        </el-row>
      </div>
    </div>
    <!-- 放置添加角色弹层组件 -->
    <el-dialog
    title="添加角色" 
    :visible.sync="showDialog"
    :append-to-body="true"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    width="600px" 
    center
    @close="close"
    >
      <!-- 表单内容 -->
      <el-form 
      label-width="120px"
      status-icon
      :model="roleForm"
      :rules="rules"
      ref="roleForm"

      >
        <el-form-item prop="name" label="角色名称：">
          <el-input v-model="roleForm.name" style="width:80%;" size="small"></el-input>
        </el-form-item>
        <!-- 重点: 即使el-switch没有配置相应的规则，但是如果要重置也需要加prop,也就是说不进行检验但是最后重置表单时直接将表单中的state字段的数据初始化（初始化取决于初始值）
        注意：
        resetFields() 是 Element UI 提供的方法，它的作用是：清除所有存在prop属性的 el-form-item 的校验状态同时，将这些字段对应的表单数据重置为初始值
         -->
        <el-form-item  label="启用：" prop="state">
          <el-switch 
          v-model="roleForm.state"
          :active-value="1"
          :inactive-value="0"
          
          > </el-switch>
        </el-form-item>

        <el-form-item prop="description"  label="角色描述：">
          <el-input style="width:80%;" size="small" v-model="roleForm.description"
          type="textarea"
          :row="4"
          ></el-input>
        </el-form-item>

        <el-form-item>
          <el-row type="flex" justify="center">
            <el-col :span="20">
              <el-button type="primary"
              style="width:25%;margin-top:10px;margin-right: 20px;margin-left: 20px;"
              @click="btnOk"
              >确定</el-button>
              <el-button 
              style="width:25%;"
              @click="close"
              >取消</el-button>
            </el-col>
          </el-row>
        </el-form-item>

      </el-form>
    </el-dialog>

     <!-- 放置分配权限弹层组件 -->
    <el-dialog
    title="分配权限" 
    :visible.sync="showPermissionDialog"
    :append-to-body="true"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    width="600px" 
    center
    >
    <!-- 放置权限数据(树形组件) -->
     <el-tree
     ref="permTree"
     v-loading="treeLoading"
     check-strictly
     :default-checked-keys="permIds"
     show-checkbox
     node-key="id" 
     :data="permissionList" 
     :props="defaultProps" 
     default-expand-all
     :expand-on-click-node="false"
     :highlight-current="true"
     @check="handleNodeCheck"
     @node-click="handleNodeClick"
     >

     </el-tree>
      <el-row slot="footer" type="flex" justify="center">
      <el-col :span="24">
        <el-button type="primary"
        style="margin-right:20px;"
        @click="btnPermissionOk"
        >确定</el-button>

        <el-button 
        @click="showPermissionDialog=false"
        >取消</el-button>
      </el-col>
    </el-row>
    </el-dialog>
  </div>
</template>
<script>
import {getRoleList,addRole,updateRole,delRole,getRoleDetail,assignPremiss} from '@/api/role'
import {getPermissionList} from '@/api/permission'
import {transListToTreeData} from '@/utils'
import {Message} from 'element-ui'
export default {
  name: 'Role',
  created(){
    this.getRoleLists()
  },
  data(){
    return {
      showDialog:false,//控制添加角色弹层隐藏
      showPermissionDialog:false,//控制添加角色弹层隐藏
      treeLoading:false,//树组件数据懒加载
      loading:false,//表格数据懒加载状态
      roleList:[],//初始化角色列表数据
      // 分页信息数据
      pageParams:{
        page:1,//当前页（初始化）
        pagesize:5,//当前页条数（初始化）
        total:0,//总数（初始化）
      },
      // 角色表单数据
      roleForm:{
        name:'',//角色名称
        description:'',//角色描述
        state:0,//角色状态->"1"-启用 "0"-未启用(默认)
      },
      // 角色表单校验规则---state不用校验
      rules:{
        name:[
          { required: true, message: '请输入角色名称', trigger: 'blur' },
          {
            pattern: /^\S{2,10}$/,
            message: '名称必须是2-10位的非空字符',
            trigger: 'blur'
          },
          
        ],
        description:[
          { required: true, message: '请输入角色描述', trigger: 'blur' },
          {
            pattern: /^\S{2,10}$/,
            message: '描述必须是2-10位的非空字符',
            trigger: 'blur'
          },

        ]
      },
      // 权限列表数据初始化
      permissionList:[],
      // 树组件属性绑定数据
      defaultProps:{
        label:'name',//要显示的字段的名字(以接口为主)
        children:'children',//读取子节点字段名(不写也行，因为默认会有children)
      },
      // 记录点击角色id
      currentRoleId:null,
      // 角色已有的权限id数据
      permIds:[]

    }
  },
  methods:{
    async getRoleLists(){
      this.loading=true//获取数据前开启加载
      const {rows,total}=await getRoleList(this.pageParams)
      // console.log(rows,total);
      this.roleList=rows//赋值数据
      this.pageParams.total=total//赋值总条数
      // 针对每一行数据加一个编辑标记
      this.roleList.forEach((item)=>{
        //给每个数据添加isEdit属性并设置为false（动态添加为静态结构非响应式）
        // item.isEdit=false----->“vue3中可以这样直接写”

        //注意：vue2中通过 this.$set(目标对象，属性名称，初始值) 可以针对目标对象添加的属性变成响应式,这样修改后才会触发视图的更新
        // 添加isEdit属性
        this.$set(item,'isEdit',false)
        // 添加editRow属性：用与缓存数据，避免修改外面的数据，和重复定义表单数据，跟行内编辑相配合
        this.$set(item,'editRow',{
          name:item.name,
          state:item.state,
          description:item.description
        })


      })
      this.loading=false//获取数据后关闭加载
      
    },
    // 切换分页时请求新的数据
    changePage(newPage){
      // console.log(newPage);
      // 将最新页码赋值给page，再次传参调用角色数据接口
      this.pageParams.page=newPage
      this.getRoleLists()
      
    },
    // 添加角色时显示弹层
    addRole(){
      this.showDialog=true
    },
    // 点击确认时调用函数
    async btnOk(){
      await this.$refs.roleForm.validate()
      // 通过校验则调用新增接口
      await addRole(this.roleForm)
      // 新增成功提示用户
      Message.success('添加成功')
      // 重新渲染页面
      this.getRoleLists()
      // 重置表单，关闭弹层
      this.close()
      
    },
    // 关闭添加角色弹层---点击取消或者叉号调用函数
    close(){
      // 重置表单
      this.$refs.roleForm.resetFields()
      // 关闭弹层
      this.showDialog=false

    },
    // 点击编辑
    btnEditRow(row){
      // console.log(row);
      row.isEdit=true//改变所点击的行的编辑状态（编辑模式）
      // 更新缓存数据
      row.editRow.name=row.name
      row.editRow.state=row.state
      row.editRow.description=row.description

    },
    // 点击编辑状态下的确定时触发
    async btnEditOk(row){
      // console.log(row.editRow)
      // 手动校验表单内容
      if(row.editRow.name&&row.editRow.description){
        // 校验通过则调用接口
        await updateRole({...row.editRow,id:row.id})
        // 更新成功，并提示
        Message.success('更新角色成功')
        // 然后更新显示数据进行回显，并退出编辑状态,也可以直接调用获取角色列表的接口函数，但是这样每次更改都要调用一次，影响性能
        /*直接调用影响性能*/ 
        // this.getRoleLists()

        /*采用数据赋值更新的方法*/
        // 方法一（不推荐）：
        // row.name=row.editRow.name
        // row.description=row.editRow.description
        // row.state=row.editRow.state
        // row.isEdit=false
        // 注意：直接这样写虽然不会报错，但是会警告，严重的可能危害系统，所以采用js中的Object.assign(目标,来源)方法进行对象赋值
        // 方法二（推荐）：
        Object.assign(row,{
          ...row.editRow,
          isEdit:false//退出编辑模式
        })//规避eslint的误判

      }else{
        Message.warning('角色和描述不能为空')
      }
      

    },
    // 点击删除确认时调用
    async confirmDel(id){
      // console.log(id);
      // 调用删除接口
      await delRole(id)
      // 提示用户
      Message.success('删除成功')
      // 注意：删除后，如果删除的是最后一个数据，此时后端没有数据，渲染前，前端还有一条数据，等后续进行回显渲染时，应该当前页码减一，渲染前一页数据，并且如果是最后一条数据但是在第一页，那么不进行页码减一，应该直接渲染，毕竟页码不能为负数
      if(this.roleList.length===1&&this.pageParams.page>1){
        this.pageParams.page--
      } 
      // 重新加载渲染数据
      this.getRoleLists()

    },
    // 点击分配权限触发
    async btnPermission(id){
      this.treeLoading=true//获取数据前开启加载
      this.showPermissionDialog=true
      // 调用权限列表数据接口
      const result=await getPermissionList()
      // 将列表数据转化成树形结构
      const resList=transListToTreeData(result,0)
      // console.log(resList,id);
      this.permissionList=resList
      // 将获取到的角色id进行存储赋值
      this.currentRoleId=id
      // 调用角色详情数据接口,并将数据里面的permIds数据解构出来，绑定给树组件相关属性，从而实现双向绑定，一点击弹出层就默认选中当前角色已有的权限
      const {permIds}=await getRoleDetail(id)
      // console.log(permIds);
      // 赋值给permIds，方便双向绑定数据响应式
      this.permIds=permIds
      this.treeLoading=false//获取数据后关闭加载
    },
    // 点击弹层选择好的权限确认时触发
    async btnPermissionOk(){
      await assignPremiss({
        id:this.currentRoleId,
        // 注意：由于树组件不能通过v-model双向绑定，只能通过它里面的方法拿到最新的选中的权限id，所以我们可以通过ref来使用里面的getCheckedKeys方法（获得选中的id数组集合）,从而实现了双向绑定，最后提交最新的数据
        permIds:this.$refs.permTree.getCheckedKeys()
      })
      // 成功后，提示用户
      Message.success('分配权限成功')
      // 关闭弹层
      this.showPermissionDialog=false
    },
    // 勾选复选框不点击对应节点时触发（该组件自带的事件回调，触发可以拿到对应参数位置的数据，详情请看文档）
    handleNodeCheck(data,checkedState){
      // console.log(data,checkedState);
      // data--->当前节点的数据，也就是后端传过来的数据
      // checkedState--->表示的是整棵树选中的状态对象，并不是整个node节点对象，所以不能使用已经绑好的key属性来获取对应id，只能通过data.id来得到

      // 由于在checkedState(全部已选中状态对象)中没有checked不能直接判断当前节点选中状态，可以用里面的checkedKeys中的值来间接判断选中状态
      const { checkedKeys }=checkedState;
      // 记录当前被选中的id
      const nodeId = data.id;
      // console.log(nodeId);
      this.$nextTick(()=>{
        //判断当前节点是否在 checkedKeys 中,根据返回的布尔值存储在isChecked中，后续根据isChecked来判断是否被勾选，如果在则为true（当前节点勾选），否则为false（当前节点未勾选）
        const isChecked = checkedKeys.includes(nodeId);
        // 获取当前被选中的节点的id，用于判断是不是对这个节点进行其它处理逻辑
        const currentKey = this.$refs.permTree.getCurrentKey();
        // console.log(currentKey);

        if (isChecked) {
          // 被勾选 → 确保当前节点高亮
          this.$refs.permTree.setCurrentKey(nodeId);
        } else {
          
          // 被取消勾选 → 如果当前高亮的是它，就清除(保险起见最好判断下，也可以不用判断currentKey === nodeId)
          if (currentKey === nodeId) {
            this.$refs.permTree.setCurrentKey(null);
          }
        }
      })
      
      
    },
    // 不勾选复选框点击对应节点时触发（该组件自带的事件回调，触发可以拿到对应参数位置的数据，详情请看文档）
    handleNodeClick(data,node){
      // console.log(data,node);
      // data--->当前节点的数据，也就是后端传过来的数据(这里用不到，但是必须写到对应位置，不然接收数据会出错,哪个位置就对应哪个位置的数据，不能省略)
      // node--->当前节点的整个节点对象数据,可以使用里面存在的各种属性
      // 注意：node.key使用前提是要先获取到整个节点对象，因为el-tree以节点的id进行了唯一标识绑定（node-key="id"），所以node.key等同于data.id

      // 1. 获取当前节点的勾选状态（node.checked 是实时的）
      const isChecked = node.checked;

      // 2. 手动切换勾选状态
      // 第三个参数 false：不深度联动父/子节点（适合 check-strictly）
      // 点击文本或者所在行切换不同的勾选状态
      this.$refs.permTree.setChecked(node, !isChecked, false);

      //3. 等待状态更新后，再决定是否高亮
      this.$nextTick(() => {
        // 通过node.key拿到所点击的节点id，再通过getNode内置方法将对应的节点数据返回储存，数据里面有实时的checked（选中状态）
        const updatedNode = this.$refs.permTree.getNode(node.key);
        // console.log(updatedNode);
        if (updatedNode?.checked) {
          // 如果勾选了，则将点击的节点高亮
          this.$refs.permTree.setCurrentKey(node.key);
        } else {
          // 如果取消勾选，且当前高亮的是它 → 取消高亮(保险起见最好判断下,不判断this.$refs.permTree.getCurrentKey() === node.key也行)
          if (this.$refs.permTree.getCurrentKey() === node.key) {
            this.$refs.permTree.setCurrentKey(null);
        }
      }
  });
      

      
    }
  }
}
</script>

<style scoped>
.role-operate{
  padding: 10px;
}
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%; /* 固定高度，或使用 100% */
  color: #999;
  font-size: 16px;
  text-align: center;
}
.empty-state i {
  font-size: 60px;
  margin-bottom: 10px;
  color: #ccc;
}
.empty-state p {
  margin: 0;
  font-size: 16px;
}
</style>
